﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Linq;
using System.Text;
using System.Threading;

using Ninject;
using Ninject.Modules;

using BlackBBS.Infrastructure.Logging;
using BlackBBS.Infrastructure.Repository;

using System.Net.NetworkInformation;
using BlackBBS.Models.Data;
using BlackBBS.Infrastructure.TelnetProtocol;

namespace BlackBBS
{
    public class Server
    {
        private IPAddress _listeningIP = null;

        public ILogger Logger { get; set; }
        public IServerRepository Repository { get; set; }

        public string HostName { get; set; }
        public IPAddress[] LocalIPAddresses { get; set; }
        public ConfigSettings BBSConfigSettings { get; set; }
        public List<Connection> Connections { get; internal set; }

        [Inject]
        public Server(ILogger logger, IServerRepository repository)
        {
            Logger = logger;
            Repository = repository;

            this.BBSConfigSettings = new ConfigSettings();
            this.BBSConfigSettings = ConfigSettings.Setup();
#if DEBUG
            this.BBSConfigSettings.BBSRootPath = @"C:\bbs\";
#endif
            Repository.Init(this.BBSConfigSettings);
            this.BBSConfigSettings.SysOp = User.GetUserByLogin(this.Repository.Users, ConfigurationManager.AppSettings["SysopLogin"]);

            this.Connections = new List<Connection>();
        }


        public void StartUp()
        {
            Logger.Info("StartUp Called");

            try
            {
                HostName = Dns.GetHostName();
                IPHostEntry ipEntry = Dns.GetHostEntry(HostName);
                LocalIPAddresses = ipEntry.AddressList;

                NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
                var nicToUse = nics.Where(x => x.GetPhysicalAddress().ToString().Equals(this.BBSConfigSettings.EthernetPhysicalAddressToUse.Replace("-",""))).FirstOrDefault();
                if (nicToUse == null)
                {
                    throw new Exception(string.Format("Invalid EthernetPhysicalAddressToUse Given, Unable to Adapter with Physical Address [{0}]", this.BBSConfigSettings.EthernetPhysicalAddressToUse));
                }
                this._listeningIP = nicToUse.GetIPProperties().UnicastAddresses.Single(addr => addr.Address.AddressFamily == AddressFamily.InterNetwork).Address;
                if (IsLocalIPValid(this._listeningIP))
                {
			        Socket listener = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
                    listener.Bind(new IPEndPoint(this._listeningIP, this.BBSConfigSettings.Port));
			        listener.Listen( 10 );
                    Logger.Info(string.Format("Listening On : [{0}] {1}:{2}", HostName, this._listeningIP, this.BBSConfigSettings.Port));
                    listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);
                }
            }
            catch (Exception ex)
            {
                Logger.Fatal(ex);
                throw;
            }
        }
        
        bool IsLocalIPValid(IPAddress ip)
        {
            if (LocalIPAddresses == null || LocalIPAddresses.Length < 1)
            {
                Logger.Error("Unable to Get Local IP Address List");
                return false;
            }
            if (!LocalIPAddresses.Contains(ip))
            {
                Logger.Error("Unable to find Given IP [" + ip.ToString() + "] in Local IP Address List");
                return false;
            }
            return true;
        }

        /// <summary>
        /// Tell any connection client server received shutdown signal
        /// </summary>
        public void Shutdown()
        {
            Logger.Info("Shutdown Called");
            foreach (var connection in this.Connections)
            {
                connection.Shutdown();
            }
        }

        /// <summary>
        /// This is called on each new telnet connection startup (or new connection) from the Server
        /// </summary>
        /// <param name="ar"></param>
        public void OnConnectRequest(IAsyncResult ar)
        {
            Socket listener = (Socket)ar.AsyncState;
            ThreadPool.QueueUserWorkItem(new WaitCallback(NewConnection), listener.EndAccept(ar));
            listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);
        }

        /// <summary>
        /// This is a callback to setup a new connection handler for the new connection
        /// </summary>
        /// <param name="sockClient"></param>
        void NewConnection(object sockClient)
        {
            var connection = new Connection(sockClient as Socket, this.Connections.Count() + 1)
            {
                Repository = this.Repository,
                Logger = this.Logger,                
                ServerSettings = this.BBSConfigSettings
            };
            connection.Connect();
            this.Connections.Add(connection);
        }
    }

}
